%{
#include "utils.h"
#include "hash.h"
#include "context.h"
#include "y.tab.h"
#include <string.h>
#include <stdio.h>

FILE* file;//Fichier LLVM
int tmp_indice = 0;
int tmp_indice2 = 0;
char tmp_inst[2000];
int indice = 1;//Indice des registres
int indLabel = 0;//Indice des labels
char unmain_inst[2000];
char main_inst[2000];
%}

%union {
    char s[1000];
    struct r t;
}

%token AND OR CLASS IF THEN ELSE END WHILE DO DEF LEQ GEQ FOR TO RETURN IN NEQ
%token <s> INT ID FLOAT STRING

%type <s> params opt_params
%type <t> stmt stmts lhs primary cond whil opt_expr expr exprs comp_expr additive_expr multiplicative_expr '(' ')'

%left '*' '/' '+' '-' '<' '>' LEQ GEQ EQ AND OR

%%

program		    : topstmts opt_terms
;

topstmts        :      
                | topstmt
                | topstmts terms topstmt
;

class           : CLASS ID {push(&c,$2);}

topstmt	        : class term stmts term END {printf("Definition d'une classe\n");strcpy(main_inst, $3.inst);}
//Etablire une correspondance classe -> méthodes (au moins new)
//                                   -> variables d'instances (commence par @ en rubic)
//                                         - O.a = 0 (préfixé par le nom de l'objet)
//objet = A.new() -> definition des methodes objet.f,objet.g et des 
                | class '<' ID term stmts END
                | stmt {strcat(main_inst, $1.inst);strcat(main_inst, "\n");}//C'est ici qu'on écrit réellement dans le fichier...
;

stmts	        : /* none */{strcpy($$.inst,"");}
                | stmt {strcpy($$.inst,$1.inst);}
                | stmts terms stmt {sprintf($$.inst,"%s\n%s",$1.inst,$3.inst);$$.type = $3.type;}
;

cond            : IF expr THEN term {
                    strcpy($$.inst,$2.inst);
                    char tmp[1000];
                    sprintf(tmp,"%%%d = load i1* %s\n",indice,$2.reg);//On charge la valeur de expr1
                    strcat($$.inst,tmp);
                    sprintf(tmp,"br i1 %%%d, label %%a%d, label %%a%d\na%d:\n",indice,indLabel,indLabel+1,indLabel);
                    strcat($$.inst,tmp);
                    indLabel++;
                    indice++;
                }
;
whil            : WHILE{tmp_indice = indice;} expr{tmp_indice2 = indice;} DO{
                    char tmp[1000];
                    strcpy($$.inst,$3.inst);
                    strcpy(tmp_inst,$3.inst);
                    sprintf(tmp,"%%%d = load %s* %s\n",indice,to_string($3.type),$3.reg);//On charge la valeur de expr1
                    strcat($$.inst,tmp);
                    sprintf(tmp,"br i1 %%%d, label %%a%d, label %%a%d\n",indice,indLabel,indLabel+1);
                    strcat($$.inst,tmp);
                    sprintf($$.reg,"%%%d",indice);
                    indice++;
}
;
/*
 * Il s'agit d'une instruction/une suite d'instructions.
 */
stmt		    : cond stmts term END {
                    strcpy($$.inst,$1.inst);
                    char tmp[1000];
                    sprintf(tmp,"%s\nbr label %%a%d\na%d:",$2.inst,indLabel,indLabel);
                    strcat($$.inst,tmp);
                    indLabel++;
                    $$.type = $2.type;
                }
                | cond stmts term ELSE term stmts term END {
                    char tmp[1000];
                    strcpy($$.inst,$1.inst);
                    strcat($$.inst,$2.inst);
                    sprintf(tmp,"\nbr label %%a%d",indLabel+1);
                    strcat($$.inst,tmp);
                    sprintf(tmp,"\na%d:\n%s\n",indLabel,$6.inst);
                    strcat($$.inst,tmp);
                    sprintf(tmp,"br label %%a%d\na%d:\n",indLabel+1,indLabel+1);
                    strcat($$.inst,tmp);
                    indLabel++;
                    indice++;
                    $$.type = $2.type;
                }
                | FOR ID IN expr TO expr DO term stmts terms END{

                    //INITIALISATION
                    strcpy($$.inst,$4.inst);//On copie l'instruction ayant permis de calculer expr
                    char tmp[1000];
                    sprintf(tmp,"%%%s = alloca %s\n",$2,to_string($4.type));//Seulement si pas définis avant...(recherche dans la table des symboles)
                    strcat($$.inst,tmp);
                    sprintf(tmp,"%%%d = load %s* %s\n",indice,to_string($4.type),$4.reg);//On charge la valeur de expr1
                    strcat($$.inst,tmp);
                    sprintf(tmp,"store %s %%%d, %s* %%%s",to_string($4.type),indice,to_string($4.type),$2);//On effectue l'affectation
                    strcat($$.inst,tmp);
                    indice++;

                    //CONDITION
                    struct r tmp2;
                    struct r tmp3;
                    tmp3.type = $4.type;
                    strcpy(tmp3.id,$2);
                    sprintf(tmp3.reg,"%%%s",$2);
                    strcpy(tmp3.inst,"");
                    print_calc(&tmp2, &tmp3, &$6, "cmp", "use", &indice);sprintf($$.id,"%s <= %s",$2,$6.id);

                    
                }
                | whil term stmts terms END {
                    strcpy($$.inst,$1.inst);
                    char tmp[1000];
                    char tmp2[1000];
                    char* tmp3;
                    while(tmp_indice < tmp_indice2){//remplacement des anciens registres
                        sprintf(tmp,"%%%d",tmp_indice);
                        sprintf(tmp2,"%%%d",indice);
                        tmp3 = replace_str(tmp_inst, tmp, tmp2);
                        strcpy(tmp_inst,tmp3);
                        free(tmp3);
                        tmp_indice++;
                        indice++;
                    }
                    sprintf(tmp,"a%d:\n%s\n%s",indLabel,$3.inst,tmp_inst);
                    strcat($$.inst,tmp);
                    sprintf(tmp,"%%%d = load i1* %%%d\n",indice,indice-1);
                    strcat($$.inst,tmp);
                    sprintf(tmp,"br i1 %%%d, label %%a%d, label %%a%d\n",indice,indLabel,indLabel+1);
                    strcat($$.inst,tmp);
                    sprintf(tmp,"a%d:\n",indLabel+1);
                    strcat($$.inst,tmp);
                    indice++;
                    indLabel+=2;
                }
                | lhs '=' expr {
                    char tmp[1000];
                    if(hash_type[hash($1.id)] != U && $3.type != hash_type[hash($1.id)]){//Conflit de types
                        sprintf(tmp,"%s of type %s but %s of type %s !", $1.id, to_string(hash_type[hash($1.id)]), $3.id, to_string($3.type));
                        yyerror(tmp);
                        return 0;
                    }
                    else if($1.id[0] > 64 && $1.id[0] < 91 && hash_type[hash($1.id)] != U){//Si c'est une constante déjà défini
                        sprintf(tmp,"%s is a constant !", $1.id);
                        yyerror(tmp);
                        return 0;
                    }
                    else{
                        if($3.type == S){// string declaree dans le unmain ? En effet (ce sont des variables globales)
                            strcpy(unmain_inst,$1.inst);//On copie l'instruction au cas où (même si il n'y en a pas...)
                            strcat(unmain_inst,$3.inst);//On copie l'instruction ayant permis de calculer expr
                            sprintf(tmp,"%s = %s",$1.reg,$3.reg);//On effectue l'affectation
                            strcat(unmain_inst,tmp);
                        }
                        else if($3.type == U){
                            sprintf(tmp,"Type of %s is undefined !", $3.id);
                            yyerror(tmp);
                            return 0;
                        }
                        else{
                        strcpy($$.inst,$1.inst);//On copie l'instruction au cas où (même si il n'y en a pas...)
                        strcat($$.inst,$3.inst);//On copie l'instruction ayant permis de calculer expr
                        if(hash_type[hash($1.id)] == U){
                            hash_type[hash($1.id)] = $3.type;//On définit le type pour l'identifiant
                            sprintf(tmp,"%s = alloca %s\n",$1.reg,to_string($3.type));//Seulement si pas définis avant...(recherche dans la table des symboles)
                            strcat($$.inst,tmp);
                        }
                        sprintf(tmp,"%%%d = load %s* %s\n",indice,to_string($3.type),$3.reg);//On effectue l'affectation
                        strcat($$.inst,tmp);
                        sprintf(tmp,"store %s %%%d, %s* %s",to_string($3.type),indice,to_string($3.type),$1.reg);//On effectue l'affectation
                        strcat($$.inst,tmp);
                        indice++;
                        }
                    }
                }
                | RETURN expr {
                    char tmp[100];
                    strcpy($$.inst,$2.inst);
                    sprintf(tmp, "ret %s* %s", to_string($2.type), $2.reg);
                    strcat($$.inst,tmp);
                    $$.type = $2.type;
                }
                | DEF ID opt_params{//Ajouter les attributs de la classe en parametre
                    char tmp[100];
                    strcpy(tmp,$3);
                    char* tok = strtok(tmp,",");
                    while(tok != NULL){
                        hash_type[hash(tok)] = F;
                        tok = strtok(NULL,",");
                    }
                    indice = 1;
                } term stmts terms END {
                    char tmp[100];
                    strcpy(tmp,c.scont);
                    strcat(tmp,$2);
                    hash_type[hash(tmp)] = Fun;
                    hash_func[hash(tmp)] = $6.type;
                    print_fct(unmain_inst,tmp,$3,$6.inst,to_string($6.type));
                    indice = 1;//On sort d'un block
                }
;

opt_params      : /* none */{strcpy($$,"");}
                | '(' ')' {strcpy($$,"");}
                | '(' params ')'{strcpy($$,$2);}
;

params          : ID ',' params {strcpy($$,$1); strcat($$,","); strcat($$,$3);}
                | ID {strcpy($$,$1);}
;

lhs             : ID {
                    //Distinguer les constantes (commence par une majuscule) (gérés au moment de l'affectation) 
                    //et les variables globales (premier caractère $)
                    if($1[0] == '$'){
                        sprintf($$.reg,"@%s",$1);//Le registre vaut @x ou @y ou .... 
                    }
                    else{
                        sprintf($$.reg,"%%%s",$1);//Le registre vaut %x ou %y ou ....
                    }
                    strcpy($$.inst, "");//Il n'y pas d'instruction
                    $$.type = hash_type[hash($1)];
                    strcpy($$.id,$1);
                }
                | ID '.' primary {strcpy($$.inst, "");}//Acces à une méthode ou à l'attribut d'une classe AJOUTER LE CONTEXTE
                | ID opt_expr {
                    char tmp[100];
                    if(hash_type[hash($1)] == U){
                        sprintf(tmp,"Undefined function %s !", $1);
                        yyerror(tmp);
                        return 0;
                    }
                    else if(hash_type[hash($1)] != Fun){
                        sprintf(tmp,"%s is not a function !", $1);
                        yyerror(tmp);
                        return 0;
                    }
                    $$.type = hash_func[hash($1)];
                    strcpy($$.inst,$2.inst);
                    if(hash_func[hash($1)] == U){
                        sprintf(tmp,"%%%d = call void @%s",indice,$1);
                    }
                    else{
                        sprintf(tmp,"%%%d = call %s* @%s",indice,to_string(hash_func[hash($1)]),$1);
                    }
                    sprintf($$.reg,"%%%d",indice);
                    indice++;
                    strcat($$.inst,tmp);
                    sprintf(tmp,"%s\n",$2.reg);
                    strcat($$.inst,tmp);
                    $$.type = hash_func[hash($1)];
                    sprintf($$.id,"%s%s",$1,$2.id);
                }//Application d'une méthode 
;

opt_expr        : '(' ')'{strcpy($$.reg,"()");}
                | '(' exprs ')' {
                    sprintf($$.id,"(%s)",$2.id);
                    strcpy($$.inst, $2.inst);
                    char tmp[100];
                    sprintf(tmp,"(%s)",$2.reg);
                    strcpy($$.reg,tmp);
                }
;

exprs           : exprs ',' expr{
                    char tmp[100];
                    strcpy($$.inst,$1.inst);
                    strcat($$.inst,$3.inst);
                    strcpy($$.reg,$1.reg);
                    sprintf(tmp,", %s* %s",to_string($3.type),$3.reg);
                    strcat($$.reg,tmp);
                    sprintf($$.id,"%s,%s",$1.id,$3.id);
                }
                | expr{
                    strcpy($$.id,$1.id);
                    char tmp[100];
                    copy(&$$,&$1);
                    sprintf(tmp,"%s* ",to_string($1.type));
                    strcat(tmp,$1.reg);
                    strcpy($$.reg,tmp);
                }
;
primary         : lhs {copy(&$$,&$1);}
                | STRING {
                    char tmp[200];
                    strcpy(tmp,$1);
                    tmp[strlen(tmp)-1] = '\0';
                    strcat(tmp,"\\00\"");
                    sprintf($$.inst, "%%%d = internal constant [%d x i8] c%s\n", indice, (int) strlen($1)-1, tmp);
                    sprintf($$.reg,"%%%d",indice);
                    $$.type = S;
                    indice++;
                }
                | FLOAT {print_init(&$$, $1, "double", &indice);}
                | INT {print_init(&$$, $1, "i32", &indice);}
                | '(' expr ')' {copy(&$$,&$2);}
;
expr            : expr AND comp_expr {print_calc(&$$, &$1, &$3, "and", "", &indice);}
                | expr OR comp_expr {print_calc(&$$, &$1, &$3, "or", "",   &indice);}
                | comp_expr {copy(&$$,&$1);}
;
comp_expr       : additive_expr '<' additive_expr {print_calc(&$$, &$1, &$3, "cmp", "slt", &indice);sprintf($$.id,"%s < %s",$1.id,$3.id);}
                | additive_expr '>' additive_expr {print_calc(&$$, &$1, &$3, "cmp", "sgt", &indice);sprintf($$.id,"%s > %s",$1.id,$3.id);}
                | additive_expr LEQ additive_expr {print_calc(&$$, &$1, &$3, "cmp", "use", &indice);sprintf($$.id,"%s <= %s",$1.id,$3.id);}
                | additive_expr GEQ additive_expr {print_calc(&$$, &$1, &$3, "cmp", "sge", &indice);sprintf($$.id,"%s >= %s",$1.id,$3.id);}
                | additive_expr EQ additive_expr  {print_calc(&$$, &$1, &$3, "cmp", "eq",  &indice);sprintf($$.id,"%s = %s",$1.id,$3.id);}
                | additive_expr NEQ additive_expr {print_calc(&$$, &$1, &$3, "cmp", "ne",  &indice);sprintf($$.id,"%s != %s",$1.id,$3.id);}
                | additive_expr {copy(&$$,&$1);}
;
additive_expr   : multiplicative_expr {copy(&$$,&$1);}
                | additive_expr '+' multiplicative_expr{print_calc(&$$, &$1, &$3, "add", "", &indice);sprintf($$.id,"%s + %s",$1.id,$3.id);}
                | additive_expr '-' multiplicative_expr{print_calc(&$$, &$1, &$3, "sub", "", &indice);sprintf($$.id,"%s - %s",$1.id,$3.id);}
;
multiplicative_expr : multiplicative_expr '*' primary {print_calc(&$$, &$1, &$3, "mul", "",  &indice);sprintf($$.id,"%s * %s",$1.id,$3.id);}
                    | multiplicative_expr '/' primary {print_calc(&$$, &$1, &$3, "div", "", &indice);sprintf($$.id,"%s / %s",$1.id,$3.id);}
                    | primary {copy(&$$,&$1);}
;
opt_terms	    : /* none */
		        | terms
;

terms		    : terms ';'
                | terms '\n'
		        | ';'
                | '\n'
;
term            : ';'
                | '\n'
;
%%

int main(int argc, char * argv[]) {
    init(hash_type, 1000);
    char strFile[100];
    sprintf(strFile, "../test/%s.ll", argv[1]);
    init_cont(&c);
    strcpy(unmain_inst,"");
    yyparse();
    file = fopen(strFile,"w+");
    fprintf(file,"%s\n",unmain_inst);
    print_begin(file);
    fprintf(file,"%s",main_inst);
    print_end(file);
    fclose(file);
    return 0;
}
